#include <signal.h>
#include <setjmp.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>	/* sleep */
#include <string.h>
#include <pthread.h>

/*
 *	以下是一个SiGEGV处理例程, 主程序注册一个信号量并创建一个线程, 线程中故意访问空指针, 引发段错误. 
 *	在信号回调中会回溯堆栈, 保存出错的地址.
 *
 *	回溯堆栈的原理在分析完整个信号处理流程后再分析, 首先我们先来分析如何使用信号. sigaction()用于向内核注册一个信号(参数1), 
 *	使用参数2(如果非空)作为注册信号的回调, 内核会将之前的信号回调返回在参数3中(如果非空). 如果父进程或程序之前阻塞了该信号
 *	则需先调用sigprocmask()取消阻塞.
 *
 *	在回调处理结束时需手动退出进程(exit()), 否则内核会不断触发该信号(重新执行异常指令再次引起崩溃), 
 *	glibc对SIGSEGV有默认的回调, 所以默认情况下也会正常退出. 
 */

#define POPCNT(data)                            do {        \
        data = (data & 0x55555555) + ((data >> 1) & 0x55555555);    \
        data = (data & 0x33333333) + ((data >> 2) & 0x33333333);    \
        data = (data & 0x0F0F0F0F) + ((data >> 4) & 0x0F0F0F0F);    \
        data = (data & 0x00FF00FF) + ((data >> 8) & 0x00FF00FF);    \
        data = (data & 0x0000FFFF) + ((data >> 16) & 0x0000FFFF);   \
    } while (0);

/**
 * we only calculate sp decrease which is static confirm in compile time
 * that is sub immediate & push instruction(and return when we find push)
 *
**/
void backtrace_stack(unsigned int **pppc, unsigned int **ppsp)
{
    unsigned int *ppc_last = *pppc;
    unsigned int *psp = *ppsp;
    unsigned int decrease = 0;
    int i;

    enum
    {
        INS_SUB_IMM = 0,
        INS_STM1,
        INS_STR_LR,
        INS_STR_FP,
        INS_BUTT
    };

    //see ARM reference manual for more detail
    struct ins_map
    {
        unsigned int mask;
        unsigned int ins;
    };

    struct ins_map map[INS_BUTT] =
    {
        {0xFFEFF000, 0xE24DD000},
        {0xFFFF4000, 0xE92D4000},
        {0xFFFFFFFF, 0xE52DE004},
        {0xFFFFFFFF, 0xE52DB004},
    };

again:
    ppc_last--;
    for (i = 0; i < INS_BUTT; i++)
    {
        if (map[i].ins == (*ppc_last &map[i].mask))
        {
            break;
        }
    }

    switch (i)
    {
    case INS_SUB_IMM:
        //sub sp, sp, imm
        decrease = (*ppc_last & 0xFF) << ((32 - 2 * (*ppc_last & 0xF00)) % 32);
        psp += decrease / sizeof(unsigned int);
        break;
    case INS_STM1:
        //push lr, ...
        decrease = *ppc_last & 0xFFFF;
        POPCNT(decrease);
        psp += decrease;
        *pppc = *(psp - 1);
        *ppsp = psp;
        return;
    case INS_STR_LR:
        //push lr
        psp += 1;
        *pppc = *(psp - 1);
        *ppsp = psp;
        return;
    case INS_STR_FP:
        //push fp
        psp += 1;
        *ppsp = psp;
        return;
    default:
        break;
    }

    goto again;
}
/**
 * process stack when catch a sigsegv:
 * ------------   stack top
 * | ......
 * | fault addr   sp position when memory fault happen
 * | sigframe     kernel use to resotre context DO NOT MODIFY(same to data)
 * | siginfo      glibc push this struct into stack(same to siginfo)
 * | current sp   sp position when enter signal handle
 *
**/
void sighandle(int sig, siginfo_t *siginfo, void *data)
{
    //data point to sigframe which is not seen to user
    //search struct ucontext in kernel for more detail
    unsigned int *psp = ((unsigned int *)data) + 21;
    unsigned int *plr = ((unsigned int *)data) + 22;
    unsigned int *ppc = ((unsigned int *)data) + 23;
    unsigned int pc_val[5] = {0};
    unsigned int sp_val[5] = {0};
    char **ppstr;
    int i;

    printf("get signal %u addr %x\n", siginfo->si_signo, siginfo->si_addr);
    pc_val[0] = *ppc;
    sp_val[0] = *psp;
    for (i = 1; i < 4; i++)
    {
        pc_val[i] = pc_val[i - 1];
        sp_val[i] = sp_val[i - 1];
        backtrace_stack((unsigned int **)(&pc_val[i]), (unsigned int **)(&sp_val[i]));
        /**
         * for subroutine use push {fp} instruction, we can't get it's caller pc
         * so we use last lr as pc and hope program won't push {fp} twice
         *
        **/
        if (pc_val[i] == pc_val[i - 1])
        {
            pc_val[i] = *plr;
        }
        pc_val[i] -= 4;
    }
    ppstr = backtrace_symbols((void **)pc_val, 5);
    for (i = 0; i < 5; i++)
    {
        printf("%u: pc[0x%08x] sp[0x%08x] %s\n", i, pc_val[i], sp_val[i], ppstr[i]);
    }
    exit(1);
}

void fault_func3()
{
    int *p = NULL;
    *p = 1;
}

void fault_func2()
{
    int a = 0x5678;
    fault_func3();
    return;
}

void fault_func1(void *pvoid)
{
    int a = 0x1234;
    fault_func2();
    return;
}

int main(int argc, char *argv[])
{
    struct sigaction sigact;
    int *p = NULL;
	pthread_t thread;

    memset(&sigact, 0, sizeof(struct sigaction));
    sigact.sa_sigaction = sighandle;
    sigact.sa_flags = SA_SIGINFO | SA_RESTART;
    sigaction(SIGSEGV, &sigact, NULL);
    getc(stdin);
    
    pthread_create(&thread, NULL, fault_func1, NULL);

    while (1)
    {
        ;
    }
    return 0;
}

#if 0

https://www.cnblogs.com/Five100Miles/p/8458732.html
以SIGSEGV为例详解信号处理(与栈回溯)

#endif
